package work.linruchang.util;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.func.Func1;
import cn.hutool.core.lang.func.LambdaUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows;

import java.lang.reflect.Field;
import java.security.InvalidParameterException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 增强的Bean处理工具
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2021/09/08
 * @since 1.8
 **/
public class EnhanceBeanUtil extends BeanUtil {

    /**
     * 将url表单字符串转成bean
     * url表单字符串：invokeSouceType=2&landlordId=15762&landlordIds=15604&landlordIds=15612&landlordIds=15762&landlordIds=15614&landlordIds=15715&landlordIds=15594&landlordIds=15682&landlordIds=15608
     * 将上述表单转成对象{ BuildingInfoReq2}
     * <p>
     * <p>
     * 容器类型仅支持List，【后续如果需要支持Set, 数组或具体某种容器请自行增强】
     *
     * @param urlFormStr
     * @param clazz
     * @param <T>
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public static <T> T urlFormStrToBean(String urlFormStr, Class<T> clazz) throws IllegalAccessException {
        T result = null;
        if (StrUtil.isNotBlank(urlFormStr) && clazz != null) {
            result = ReflectUtil.newInstance(clazz);
            List<String> keyValues = StrUtil.split(urlFormStr, "&");
            for (String keyValue : keyValues) {
                List<String> keyValueList = StrUtil.split(keyValue, "=");
                if (CollUtil.size(keyValueList) != 2) {
                    throw new InvalidParameterException("表单格式必须是key=value，请检查");
                } else {
                    String key = keyValueList.get(0);
                    String value = keyValueList.get(1);
                    Field field = ObjectUtil.defaultIfNull(ReflectUtil.getField(clazz, key), ReflectUtil.getField(clazz, EnhanceStrUtil.camelTransfersBetweenUnderline(key)));
                    if (field != null) {
                        field.setAccessible(true);
                        Class<?> valueType = field.getType();

                        // 目标Bean字段是容器
                        if (Collection.class.isAssignableFrom(valueType)) {

                            // 获取容器元素的类型
                            Class collectionElemType = EnhanceClassUtil.getCollElemType(field);
                            if (valueType == List.class) {
                                List keyFieldValues = (List) field.get(result);
                                if (keyFieldValues == null) {
                                    keyFieldValues = new ArrayList();
                                    field.set(result, keyFieldValues);
                                }
                                if (collectionElemType == Object.class) {
                                    keyFieldValues.add(value);
                                } else {
                                    keyFieldValues.add(EnhanceTypeUtil.convertValue(collectionElemType, value));
                                }
                            }

                            // 非容器对象
                        } else {
                            Object targetTypeValue = EnhanceTypeUtil.convertValue(valueType, value);
                            field.set(result, targetTypeValue);
                        }
                    }
                }
            }

        }

        return result;
    }


    public static <T> List<T> mapToBeans(List<Map> maps, Class<T> clazz) {
        List<T> result = Collections.emptyList();
        if (CollUtil.isNotEmpty(maps)) {
            result = new ArrayList<>();
            for (Map map : maps) {
                result.add(mapToBean(map, clazz, true));
            }
        }
        return result;
    }

    public static Map<String, Object> beanToMap(Object bean, final String[] needKeyNames, boolean ignoreEmptyValue) {
        return beanToMap(bean, needKeyNames, ignoreEmptyValue, false);
    }

    /**
     * 将Bean转成Map
     *
     * @param bean              待转换的Bean
     * @param needKeyNames      筛选出需要的key字段，空则需要全部
     * @param ignoreEmptyValue  是否忽略Value是Empty的键值对   { ObjectUtil#isNotEmpty(Object)}
     * @param ignoreRawDataFlag 是否忽略基本类型字段
     * @return
     */
    public static Map<String, Object> beanToMap(Object bean, final String[] needKeyNames, boolean ignoreEmptyValue, boolean ignoreRawDataFlag) {
        return beanToMap(bean, needKeyNames, ignoreEmptyValue, ignoreRawDataFlag, false);
    }

    /**
     * 将Bean转成Map
     *
     * @param bean                      待转换的Bean
     * @param needKeyNames              筛选出需要的key字段，空则需要全部
     * @param ignoreEmptyValue          是否忽略Value是Empty的键值对   { ObjectUtil#isNotEmpty(Object)}
     * @param ignoreRawDataFlag         是否忽略基本类型字段
     * @param ignoreDefaulValueDataFlag 是否忽略含有默认值的key/Vale
     * @return
     */
    @SneakyThrows
    public static Map<String, Object> beanToMap(Object bean, final String[] needKeyNames, boolean ignoreEmptyValue, boolean ignoreRawDataFlag, boolean ignoreDefaulValueDataFlag) {
        Map<String, Object> result = BeanUtil.beanToMap(bean, false, ignoreEmptyValue);
        if (ignoreEmptyValue) { // 筛选出不空的字段
            result = MapUtil.filter(result, new Filter<Map.Entry<String, Object>>() {
                @Override
                public boolean accept(Map.Entry<String, Object> entry) {
                    return ObjectUtil.isNotEmpty(entry.getValue());
                }
            });
        }

        if (ArrayUtil.isNotEmpty(needKeyNames)) {  // 筛选出需要的字段
            result = MapUtil.filter(result, new Filter<Map.Entry<String, Object>>() {
                @Override
                public boolean accept(Map.Entry<String, Object> entry) {
                    return ArrayUtil.contains(needKeyNames, entry.getKey());
                }
            });
        }

        // 排除基本类型的字段名
        if (ignoreRawDataFlag && bean != null) {
            final Set<String> rawFieldNameSet = new HashSet<>();
            Field[] fields = ReflectUtil.getFields(bean.getClass());
            for (Field field : fields) {
                field.setAccessible(true);
                if (field.getType().isPrimitive()) {
                    rawFieldNameSet.add(field.getName());
                }
            }
            result = MapUtil.filter(result, new Filter<Map.Entry<String, Object>>() {
                @Override
                public boolean accept(Map.Entry<String, Object> entry) {
                    return !CollUtil.contains(rawFieldNameSet, entry.getKey());
                }
            });
        }

        // 排除含有默认值的字段
        if (ignoreDefaulValueDataFlag && bean != null) {
            final Map<String, Object> defaultValueMap = MapUtil.filter(BeanUtil.beanToMap(bean.getClass().newInstance()), new Filter<Map.Entry<String, Object>>() {
                @Override
                public boolean accept(Map.Entry<String, Object> keyValueEntry) {
                    return keyValueEntry.getValue() != null;
                }
            });

            result = MapUtil.filter(result, new Filter<Map.Entry<String, Object>>() {
                @Override
                public boolean accept(Map.Entry<String, Object> entry) {
                    return !CollUtil.contains(defaultValueMap.keySet(), entry.getKey());
                }
            });

        }


        return result;
    }

    /**
     * 将Bean转成Map
     *
     * @param bean              待转换的Bean
     * @param ignoreEmptyValue  是否忽略Value是Empty的键值对   { ObjectUtil#isNotEmpty(Object)}
     * @param ignoreRawDataFlag 是否忽略是基本类型的字段
     * @return
     */
    public static Map<String, Object> beanToMap(Object bean, boolean ignoreEmptyValue, boolean ignoreRawDataFlag, boolean ignoreDefaulValueDataFlag) {
        return beanToMap(bean, new String[0], ignoreEmptyValue, ignoreRawDataFlag, ignoreDefaulValueDataFlag);
    }

    /**
     * 将Bean转成Map
     *
     * @param bean             待转换的Bean
     * @param ignoreEmptyValue 是否忽略Value是Empty的键值对   { ObjectUtil#isNotEmpty(Object)}
     * @return
     */
    public static Map<String, Object> beanToMap(Object bean, boolean ignoreEmptyValue) {
        // return beanToMap(bean, null, ignoreEmptyValue);
        return beanToMap(bean, new String[0], ignoreEmptyValue, false);
    }


    /**
     * 复制Bean对象属性<br>
     * 限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
     *
     * @param source           源Bean对象
     * @param target           目标Bean对象  == 拷贝后如果发现有的字段依然是空则会赋予默认值
     * @param ignoreProperties 忽略的源Bean字段
     * @param fieldMapping     字段映射
     */
    public static <S, T> void copyPropertiesByDefault(S source, T target, List<Func1<S, ?>> ignoreProperties, Map<Func1<S, ?>, Func1<T, ?>> fieldMapping) {

        List<String> ignorePropertieNames = EnhanceLambdaUtil.getFieldNames(ignoreProperties);
        String[] ignorePropertieArrayNames = ArrayUtil.toArray(ignorePropertieNames, String.class);

        Map<String, String> fieldNameMapping = MapUtil.emptyIfNull(fieldMapping).entrySet().stream()
                .collect(Collectors.toMap(mapEntry -> LambdaUtil.getFieldName(mapEntry.getKey()), mapEntry -> LambdaUtil.getFieldName(mapEntry.getValue())));


        copyProperties(source, target, CopyOptions.create()
                .setIgnoreProperties(ignorePropertieArrayNames)
                .setFieldMapping(fieldNameMapping)
                .setIgnoreNullValue(true));

        if (target != null && (!(target instanceof Map))) {
            Stream.of(ReflectUtil.getFields(target.getClass()))
                    .filter(field -> ReflectUtil.getFieldValue(target, field) == null)
                    .forEach(field -> {
                        if (field.getType() == String.class) {
                            ReflectUtil.setFieldValue(target, field, StrUtil.EMPTY);
                        }
                    });
        }

    }

    /**
     * 获取Bean的属性字段
     * @param bean 实体Bean
     * @param fieldName 属性名
     */
    public static <T, F> F getProperty(T bean, Func1<T, F> fieldName) {
        return getProperty(bean, LambdaUtil.getFieldName(fieldName));
    }

}
